use actix_web::{HttpRequest, HttpResponse, Responder, get, post, web};
use base64::{Engine, prelude::BASE64_STANDARD};
use chrono::{Duration, Utc};

use serde_json::json;

use crate::{
    AppState,
    model::{FileSearchPageParam, FileSearchParam, UploadFile, UploadToken},
    service::{self, auth},
};

#[get("/{id}")]
async fn find_by_id(req: HttpRequest, app_state: web::Data<AppState>) -> impl Responder {
    let id: i64 = req.match_info().get("id").unwrap().parse().unwrap();
    let result = service::file::find_by_id(&app_state.database_connection, id)
        .await
        .unwrap();

    HttpResponse::Ok().json(result)
}

#[post("/search")]
async fn search(
    app_state: web::Data<AppState>,
    search_param: web::Json<FileSearchParam>,
) -> impl Responder {
    let result = service::file::search(&app_state.database_connection, &search_param)
        .await
        .unwrap();

    HttpResponse::Ok().json(result)
}

#[post("/search/page")]
async fn search_page(
    app_state: web::Data<AppState>,
    search_param: web::Json<FileSearchPageParam>,
) -> impl Responder {
    let page_result = service::file::search_page(&app_state.database_connection, &search_param)
        .await
        .unwrap();

    HttpResponse::Ok().json(page_result)
}

#[post("/upload/token")]
async fn upload_token(
    app_state: web::Data<AppState>,
    upload_file_param: web::Json<UploadFile>,
) -> impl Responder {
    let db = &app_state.database_connection;

    let bucket = service::bucket::find_by_name(db, &upload_file_param.bucket)
        .await
        .unwrap();

    if bucket.is_none() {
        return HttpResponse::BadRequest().body("空间不存在");
    }

    let bucket = &bucket.unwrap();

    let app = service::app::find_by_id(db, bucket.app_id).await.unwrap();

    if app.is_none() {
        return HttpResponse::BadRequest().body("应用不存在");
    }

    let app = &app.unwrap();

    let file_key = service::file::generate_file_key(&upload_file_param.0);
    let key = file_key.clone();
    let policy_file_key = file_key.clone();
    let source_file = upload_file_param.source_file.clone().unwrap_or_default();
    let source_file_size = upload_file_param
        .source_file_size
        .clone()
        .unwrap_or_default();
    let source_file_type = upload_file_param
        .source_file_type
        .clone()
        .unwrap_or_default();
    let source_file_attr = upload_file_param
        .source_file_attr
        .clone()
        .unwrap_or_default();

    let file_id = service::file::create(
        db,
        bucket.id,
        &file_key,
        &source_file,
        source_file_size,
        &source_file_type,
        &source_file_attr,
    )
    .await
    .unwrap();

    let upload_url = format!("//{}", bucket.domain);
    let mut expired_in_sec = 60 * 60_i64;
    let mut expiration = (Utc::now() + Duration::minutes(60)).to_rfc3339();

    if let Some(sec) = upload_file_param.expired_in_sec
        && sec > 0
    {
        expired_in_sec = sec;
        expiration = (Utc::now() + Duration::seconds(sec)).to_rfc3339();
    }

    let policy =BASE64_STANDARD.encode(json!(
        {"expiration":expiration,"conditions":[{"bucket":bucket.name},["eq","$key",policy_file_key]]}
    ).to_string());

    let signature = auth::signature_str(&app.access_key_secret, &policy);

    let url = service::file::get_url(
        &app.access_key_id,
        &app.access_key_secret,
        &bucket.name,
        bucket.bucket_type,
        &bucket.domain,
        &key,
        expired_in_sec,
    );

    HttpResponse::Ok().json(UploadToken {
        file_id: file_id,
        upload_url: upload_url,
        access_key_id: app.access_key_id.clone(),
        policy: policy,
        signature: signature,
        key: key,
        url: url,
    })
}

pub(crate) fn routers(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/v1/files")
            .service(find_by_id)
            .service(search)
            .service(search_page)
            .service(upload_token),
    );
}
